<?php

namespace Vier\Ethtool;

use Web3\Utils;
use Web3\Web3;
use Web3\Providers\HttpProvider;
use Web3\RequestManagers\HttpRequestManager;
use Web3\Contract;

/**
 * TokenBase
 */
class TokenBase{

    protected static $default_config;

    public $web3;
    public $contract;
    protected $credential;
    protected $managerKey;

    protected $addr;
    protected $abi;

    protected $ChainApiUrl;
    protected $NetworkId;

    protected $monitor;

    public static function setDefault($conf){
        self::$default_config=$conf;
    }

    function __construct($conf){

        $this->managerKey = $conf['ManagerKey']?:self::$default_config['ManagerKey'];
        $this->NetworkId = $conf['NetworkId']?:self::$default_config['NetworkId'];
        $this->ChainApiUrl = $conf['ChainApiUrl']?:self::$default_config['ChainApiUrl'];

        $timeout = 10;

        $this->web3 = new Web3(new HttpProvider(new HttpRequestManager($this->ChainApiUrl,$timeout)));
        $this->contract = new Contract($this->web3->provider,$this->abi);
        $this->abi = $conf['abi'];
        $conf['addr'] && $this->at($conf['addr']);

        $this->monitor=[];
    }

    public function at($address){
        $this->addr = $address;
        return $this->contract->at($this->addr);
    }

    private function newTopicFilter(){
        $cb = new Callback;
        $this->contract->eth->newFilter(['address'=>$this->addr],$cb);
        return $cb->result;
    }

    /**
     * @method onEvent 添加一个事件监听
     * @param $eventName 事件名称
     * @param $callback 回调
     */
    public function onEvent($eventName,$callback){
        $this->monitor[$eventName][] = function($log,$inputs)use($callback){
                $idx=0;
                foreach ($inputs as &$input) {
                    if ($input['indexed']) {
                        $idx++;
                        $input['value'] = $this->contract->ethabi->decodeParameter(
                                                $input['type'],
                                                $log['topics'][$idx]
                                            );
                    }else{
                        $input['value'] = $this->contract->ethabi->decodeParameter(
                                                $input['type'],
                                                $log['data']
                                            );
                    }
                }
                call_user_func_array(
                        $callback,
                        array_merge(
                                [$log],
                                array_column($inputs,'value')
                            )
                    );
            };
    }

    /**
     * @method monitorLoop 启动监听轮询
     * @method $micro_seconds 轮询间隔 单位毫秒
     */
    public function monitorLoop($micro_seconds=1000){
        $cb = new Callback;

        $events = array_keys(array_filter($this->monitor));
        $topics = array_map(function($e){
                return $this->contract->ethabi->encodeEventSignature(
                        $this->contract->events[$e]
                    );
            },$events);
        $events = array_combine($topics,$events);

        $fid = $this->newTopicFilter();
        echo $fid . PHP_EOL;//

        // TODO:
        for ($i=0; 1; $i++) {
            $this->contract->eth->getFilterChanges($fid,$cb);
            $logs = $cb->result?:[];

            foreach($logs as $log){
                if(!in_array($log->topics[0],$topics)){
                    continue;//其它事件 跳过
                }
                $e = $this->contract->events[$events[$log->topics[0]]];

                foreach ($this->monitor[$e['name']] as $fn) {
                    $fn((array)$log,$e['inputs']);
                }
            }
            usleep($micro_seconds);
        }
    }

    /**
     * @method setOperator 设置合约操作人
     * @param $operator 操作者钱包私钥
     */
    public function setOperator($operator){
        if (is_string($operator)) {
            if (strlen($operator)==64||strlen($operator)==66) {
                $this->credential = Credential::fromKey($operator);
                return;
            }
        }elseif ($t instanceof Credential) {
            $this->credential = $operator;
            return;
        }
        throw new Exception("Error: operator.", 1);
    }

    public function blockNumber(){
        $cb = new Callback;
        $this->contract->eth->blockNumber($cb);
        return \Web3\Utils::toBn($cb->result)->toString();
    }

    /**
     * @method getTransactionByHash 获取交易信息
     */
    public function getTransactionByHash($txhash){
        $cb = new Callback;
        $this->contract->eth->getTransactionByHash($txhash,$cb);
        $cb->result = (array)$cb->result;
        if ($cb->result) {
            $cb->result['blockConfirmations'] = $this->blockNumber()-\Web3\Utils::toBn($cb->result['blockNumber'])->toString()+1;
        }
        return $cb->result;
    }

    /**
     * @method getTransactionReceipt 获取交易回执
     */
    public function getTransactionReceipt($txhash){
        $cb = new Callback;
        $this->contract->eth->getTransactionReceipt($txhash,$cb);
        $cb->result = (array)$cb->result;
        if ($cb->result) {
            $cb->result['blockConfirmations'] = $this->blockNumber()-\Web3\Utils::toBn($cb->result['blockNumber'])->toString()+1;
            $cb->result['logs'] = array_map(function($log){
                return (array)$log;
            },$cb->result['logs']);
        }
        return $cb->result;
    }

    /**
     * @method waitForReceipt 等待回执
     */
    public function waitForReceipt($txhash,$timeout=60,$interval=1){
        $r = null;
        $t0 = time();
        while(true){
            $r = $this->getTransactionReceipt($txhash);
            if($r) break;
            $t1 = time();
            if(($t1 - $t0) > $timeout) break;
            sleep($interval);
        }
        return $r;
    }

    public function send(){
        $cb = new Callback;
        $this->contract->eth->getTransactionCount($this->credential->getAddress(),'latest',$cb);
        $nonce =  $cb->result;
        $opts = [
            'nonce'   => Utils::toHex($nonce,true),
            'from'    => $this->credential->getAddress(),
            'gas'     => Utils::toHex(2000000,true),
            'gasPrice'=> '0x'.Utils::toWei('20','gwei')->toHex(),
            'gasLimit'=> Utils::toHex(2000000,true),
            'to'      => $this->contract->getToAddress(),
            'value'   => '0x0',
            'data'    => '0x'.call_user_func_array([$this->contract,'getData'],func_get_args()),
            'chainId' => $this->NetworkId,
        ];
        $req = $this->credential->signTransaction($opts);
        $this->contract->eth->sendRawTransaction($req,$cb);
        $txhash = $cb->result;
        return $txhash;
        // return $this->waitForReceipt($txhash);
    }

	public function call(){
        $cb = new Callback;
        $opts = [];
        $params = func_get_args();
        $params[] = $opts;
        $params[] = $cb;
        call_user_func_array([$this->contract,'call'],$params);
        return $cb->result;
	}

    // // ---------------------- 信息查询 ----------------------

    // /**
    //  * @method owner 合约管理员
    //  */
    // public function owner(){
    //     $r = $this->call('owner');
    //     return $r[0];
    // }

    // public function paused(){
    //     $r = $this->call('paused');
    //     return $r[0];
    // }

    // public function supportsInterface($interfaceId){
    //     $r = $this->call('supportsInterface',$interfaceId);
    //     return $r[0];
    // }

    // // ---------------------- 合约操作 ----------------------

    // /**
    //  * @method transferOwnership 转移合约管理权限
    //  * @param newOwner 新的管理员
    //  */
    // public function transferOwnership($newOwner){
    //     $this->setOperator($this->managerKey);
    //     return $this->send('newOwner',$newManager);
    // }

    // /**
    //  * @method renounceOwnership 放弃管理权限
    //  */
    // public function renounceOwnership(){
    //     $this->setOperator($this->managerKey);
    //     return $this->send('renounceOwnership',$newManager);
    // }

    // public function pause(){
    //     $this->setOperator($this->managerKey);
    //     return $this->send('pause');
    // }

    // public function unpause(){
    //     $this->setOperator($this->managerKey);
    //     return $this->send('unpause');
    // }

    // // ----------------------
}